{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 自定义工具及其应用\n",
    "https://python.langchain.com/docs/use_cases/tool_use/prompting/"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "#导入语言模型\n",
    "import os\n",
    "from langchain_community.llms import Tongyi\n",
    "from langchain_community.llms import SparkLLM\n",
    "from langchain_community.llms import QianfanLLMEndpoint\n",
    "\n",
    "import pandas as pd\n",
    "#导入模版\n",
    "from langchain.prompts import PromptTemplate\n",
    "\n",
    "#导入聊天模型\n",
    "from langchain.prompts.chat import (\n",
    "    ChatPromptTemplate,\n",
    "    SystemMessagePromptTemplate,\n",
    "    AIMessagePromptTemplate,\n",
    "    HumanMessagePromptTemplate,\n",
    ")\n",
    "from langchain.schema import (\n",
    "    AIMessage,\n",
    "    HumanMessage,\n",
    "    SystemMessage\n",
    ")\n",
    "\n",
    "from langchain_community.chat_models import ChatSparkLLM\n",
    "from langchain_community.chat_models.tongyi import ChatTongyi\n",
    "from langchain_community.chat_models import QianfanChatEndpoint\n",
    "\n",
    "#输入三个模型各自的key\n",
    "\n",
    "os.environ[\"DASHSCOPE_API_KEY\"] = \"\"\n",
    "\n",
    "os.environ[\"IFLYTEK_SPARK_APP_ID\"] = \"\"\n",
    "os.environ[\"IFLYTEK_SPARK_API_KEY\"] = \"\"\n",
    "os.environ[\"IFLYTEK_SPARK_API_SECRET\"] = \"\"\n",
    "\n",
    "os.environ[\"QIANFAN_AK\"] = \"\"\n",
    "os.environ[\"QIANFAN_SK\"] = \"\"\n",
    "\n",
    "from operator import itemgetter\n",
    "from langchain_core.runnables import RunnableLambda, RunnablePassthrough\n",
    "from langchain_core.output_parsers import StrOutputParser"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "model_ty = Tongyi(temperature=0)\n",
    "model_qf = QianfanLLMEndpoint(temperature=0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'0.1.9'"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import langchain\n",
    "langchain.__version__"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "#! pip install --upgrade langchain -i https://mirrors.aliyun.com/pypi/simple"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#! pip install langchainhub -i https://mirrors.aliyun.com/pypi/simple"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 单个自定义工具及其使用\n",
    "\n",
    "* 装饰器\n",
    "\n",
    "Python装饰器起以下主要作用：\n",
    "\n",
    "1. **添加功能**：装饰器能够在不修改原函数或类代码的基础上，为其添加新的功能。这包括但不限于日志记录、性能监控、权限控制、缓存、事务处理、数据验证、输入/输出格式化等。\n",
    "\n",
    "2. **模块化与复用**：装饰器将通用的、与业务逻辑分离的功能封装为独立的模块（即装饰器函数），使得这些功能可以方便地应用于多个不同的函数或方法，提高代码的模块化程度和复用性。\n",
    "\n",
    "3. **保持代码整洁**：使用装饰器可以避免在函数主体中混杂非核心逻辑，保持函数专注于其核心职责，从而提高代码的可读性和可维护性。通过在函数定义前使用`@装饰器名`的语法糖，装饰器的使用显得简洁且直观。\n",
    "\n",
    "4. **无侵入式扩展**：装饰器允许在不直接修改已有代码的前提下，对函数的行为进行扩展。这意味着即使在后期需要添加、修改或移除装饰器提供的功能，也不会影响到函数本身的定义，有利于遵循“开闭原则”（Open/Closed Principle），即对扩展开放，对修改关闭。\n",
    "\n",
    "5. **保留原函数接口**：装饰器返回的新函数通常会包裹原函数的调用，确保新函数对外暴露的接口（如参数列表、返回值类型）与原函数保持一致，用户无需因添加装饰器而调整对目标函数的调用方式。\n",
    "\n",
    "6. **类型检查、日志记录、性能测试**：装饰器常用于进行类型检查以确保函数接收正确的参数类型；在函数执行前后插入日志语句，记录函数调用的相关信息，如函数名、参数、执行时间等，用于调试和性能分析；计算函数的执行时间，帮助识别和优化性能瓶颈。\n",
    "\n",
    "7. **权限控制**：在Web开发中，装饰器可用于实现用户认证和授权，确保只有具备相应权限的用户才能访问特定的路由或资源。\n",
    "\n",
    "综上所述，Python装饰器是一种强大的工具，用于在不破坏原有代码结构的情况下，为函数或类添加额外行为、执行预处理或后处理任务、统一管理横切关注点（cross-cutting concerns），从而提升代码的组织性、灵活性和可维护性。\n",
    "\n",
    "\n",
    "### langchain中的tool\n",
    "\n",
    "在LangChain中,`tool`装饰器的作用是将一个Python函数转换为`Tool`对象,以便在代理(Agent)执行时作为工具使用。\n",
    "\n",
    "`Tool`对象描述了一个可执行的具体操作,包括名称、描述、函数指针以及函数所需的输入示例等信息。代理在执行时,可以根据当前的目标和环境,选择合适的工具并调用其函数来完成任务。\n",
    "\n",
    "使用`tool`装饰器的步骤如下:\n",
    "\n",
    "1. 定义一个Python函数,该函数完成某个特定的操作,如进行数学计算、查询API等。\n",
    "\n",
    "2. 使用`@tool`装饰器对该函数进行装饰,提供一些关于该工具的元数据,如名称、描述、输入示例等。\n",
    "\n",
    "3. 将装饰后的函数传递给各种langchain内置代理然后通过`AgentExecutor`执行。\n",
    "\n",
    "例如:\n",
    "\n",
    "```python\n",
    "from langchain.tools import tool\n",
    "\n",
    "@tool\n",
    "def calculator(operation, num1, num2):\n",
    "    \"\"\"\n",
    "    Performs a simple calculation with the given operation and numbers.\n",
    "    \"\"\"\n",
    "    operations = {\"加\": lambda a, b: a + b,\n",
    "                  \"减\": lambda a, b: a - b, \n",
    "                  \"乘\": lambda a, b: a * b,\n",
    "                  \"除\": lambda a, b: a / b}\n",
    "    \n",
    "    try:\n",
    "        op_func = operations[operation]\n",
    "        result = op_func(num1, num2)\n",
    "        return f\"{num1} {operation} {num2} = {result}\"\n",
    "    except KeyError:\n",
    "        return f\"Operation {operation} not supported\"\n",
    "```\n",
    "\n",
    "在上面的例子中,我们定义了一个`calculator`函数,用于执行基本的数学运算。通过`@tool`装饰器,该函数被转换为一个`Tool`对象,具有名称\"calculator\"、描述\"Performs a simple calculation...\"以及输入示例。然后,我们可以将该工具传递给代理:在执行时,代理会根据当前目标选择合适的工具,并调用其函数来完成任务。\n",
    "\n",
    "总之,`tool`装饰器让我们可以方便地将Python函数转换为可供代理使用的工具,从而扩展代理的功能,使其能够执行更多种类的任务。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.tools import tool\n",
    "\n",
    "@tool\n",
    "def multiply(first_int: int, second_int: int) -> int:\n",
    "    \"\"\"将两个整数相乘。\"\"\"\n",
    "    return first_int * second_int"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "multiply\n",
      "multiply(first_int: int, second_int: int) -> int - 将两个整数相乘。\n",
      "{'first_int': {'title': 'First Int', 'type': 'integer'}, 'second_int': {'title': 'Second Int', 'type': 'integer'}}\n"
     ]
    }
   ],
   "source": [
    "print(multiply.name)\n",
    "print(multiply.description)\n",
    "print(multiply.args)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "20"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "multiply.invoke({\"first_int\": 4, \"second_int\": 5})\n",
    "#ChatTongyi()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'multiply: multiply(first_int: int, second_int: int) -> int - 将两个整数相乘。'"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from langchain.tools.render import render_text_description\n",
    "rendered_tools = render_text_description([multiply])\n",
    "rendered_tools"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.prompts import ChatPromptTemplate\n",
    "\n",
    "rendered_tools = render_text_description([multiply])\n",
    "\n",
    "system_prompt = f\"\"\"您是一名助理，可以使用以下工具集。 以下是每个工具的名称和说明:\n",
    "\n",
    "{rendered_tools}\n",
    "\n",
    "根据用户输入，返回要使用的工具的名称和输入。 将您的响应作为带有“name”和“arguments”键的 JSON blob 返回.\"\"\"\n",
    "\n",
    "prompt = ChatPromptTemplate.from_messages(\n",
    "    [(\"system\", system_prompt), (\"user\", \"{input}\")]\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "bool(1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### JsonOutputParser与自定义工具的参数传递\n",
    "\n",
    "`JsonOutputParser`是Langchain中用于解析JSON输出的一个函数。它的作用是将AI生成的JSON格式的输出解析为Python对象,以便在后续处理中使用。\n",
    "\n",
    "对于输入的JSON格式,`JsonOutputParser`要求输入为有效的JSON字符串。一般来说,该JSON字符串应该包含一个字典作为顶层对象,其中包含相关的键值对。\n",
    "\n",
    "* `JsonOutputParser`是一个runable的函数，通常都用在链里负责给工具传递大模型回答所产生的参数\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "#例如,假设我们有一个AI模型生成了如下JSON输出:\n",
    "json_str=\"\"\"```json\n",
    "{\n",
    "  \"result\": \"2 + 2 = 4\",\n",
    "  \"mode\": \"math_operation\"\n",
    "}\n",
    "```\"\"\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'result': '2 + 2 = 4', 'mode': 'math_operation'}\n"
     ]
    }
   ],
   "source": [
    "from langchain_core.output_parsers import JsonOutputParser\n",
    "\n",
    "#我们可以使用`JsonOutputParser`将其解析为Python字典:\n",
    "#如您所见,`JsonOutputParser`将JSON字符串解析为了一个Python字典对象。\n",
    "\n",
    "parser = JsonOutputParser()\n",
    "output_dict = parser.parse('{\"result\": \"2 + 2 = 4\", \"mode\": \"math_operation\"}')\n",
    "#output_dict = parser.parse(json_str)\n",
    "print(output_dict)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "AIMessage(content='```json\\n{\\n  \"name\": \"multiply\",\\n  \"arguments\": {\\n    \"first_int\": 3,\\n    \"second_int\": 4\\n  }\\n}\\n```')"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "model = ChatTongyi()\n",
    "chain = prompt | model \n",
    "#| JsonOutputParser()\n",
    "chain.invoke({\"input\": \"3乘以4等于\"})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "12"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "chain = prompt | model | JsonOutputParser() | itemgetter(\"arguments\") | multiply\n",
    "chain.invoke({\"input\": \"3乘以4等于\"})"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### classwork 1\n",
    "\n",
    "* 注册聚合数据账号账号并申请天气预报api接口，尝试在python中调用接口，相关指导视频如下：\n",
    "\n",
    "https://www.juhe.cn/\n",
    "\n",
    "* 基于此接口封装一个自定义工具\n",
    "\n",
    "* 基于此工具完成一个出行天气顾问的代理应用"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [],
   "source": [
    "import requests\n",
    "import json"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [],
   "source": [
    "#resp_json"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [],
   "source": [
    "@tool\n",
    "def tianqi(city: str) -> dict:\n",
    "    \"\"\"查询最近几日的天气情况，包括温度，天气，湿度，风向等\"\"\"\n",
    "    headers ={\"Content-Type\": \"application/x-www-form-urlencoded\"}\n",
    "    url = \"http://apis.juhe.cn/simpleWeather/query\"\n",
    "    params = {\n",
    "        \"key\":\"\", # 在个人中心->我的数据,接口名称上方查看\n",
    "        \"city\":city, # 要查询的城市名称或城市ID\n",
    "    }\n",
    "    resp = requests.get(url,params,headers=headers)\n",
    "    resp_json = json.loads(resp.text)\n",
    "    return resp_json"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [],
   "source": [
    "#tianqi.invoke({\"city\":\"杭州\"})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 81,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tianqi\n",
      "tianqi(city: str) -> dict - 查询最近几日的天气情况，包括温度，天气，湿度，风向等\n",
      "{'city': {'title': 'City', 'type': 'string'}}\n"
     ]
    }
   ],
   "source": [
    "print(tianqi.name)\n",
    "print(tianqi.description)\n",
    "print(tianqi.args)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 64,
   "metadata": {},
   "outputs": [],
   "source": [
    "#此JSON blob需要有如下格式：```json...```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [],
   "source": [
    "template_tianqi =\"\"\"根据下面问题和json格式的响应，编写一个针对此问题自然语言回应：\n",
    "问题：{question}\n",
    "响应：{response}\"\"\"\n",
    "prompt_response_tianqi = ChatPromptTemplate.from_template(template_tianqi)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [],
   "source": [
    "#chain0.invoke({\"input\": \"明天杭州出去需要带伞吗\"})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'亲爱的，明天你去北京玩真是太棒了！记得带上一件轻薄的外套，因为白天温度大约在21度，但早晚可能会有些凉意。白天阳光明媚，空气质量相对较好，AQI为57，记得戴上口罩哦。未来几天天气预报显示都是晴朗的好天气，16号是西南风转北风，17号则是南风转东北风，记得适时调整衣物以防风。18号和19号气温会稍微升高，19号会有少许多云，但别担心，20号又会回归晴朗，温度适中。总的来说，北京的春天既美丽又宜人，记得拍下美美的照片分享给我，安全出行，玩得开心哦！'"
      ]
     },
     "execution_count": 24,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "chain1.invoke(\"女朋友明天要去北京玩，能否以我的口吻给她些出行建议，务必让她感动！\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### classwork 2\n",
    "\n",
    "* 注册聚合数据账号账号并申请万年历api接口，尝试在python中调用接口，相关指导视频如下：\n",
    "\n",
    "https://www.juhe.cn/\n",
    "\n",
    "* 基于此接口封装一个自定义工具\n",
    "\n",
    "* 基于此工具完成一个农历休假顾问的代理应用"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [],
   "source": [
    "@tool\n",
    "def wannianli(date: str) -> dict:\n",
    "    \"根据指定日期查询其农历，习俗，星期几，假期，生肖\"\n",
    "    date=date.replace(\"-0\",\"-\")\n",
    "    print(date)\n",
    "    headers ={\"Content-Type\": \"application/x-www-form-urlencoded\"}\n",
    "    url = \"http://v.juhe.cn/calendar/day\"\n",
    "    params = {\n",
    "        \"key\":\"\", # 在个人中心->我的数据,接口名称上方查看\n",
    "        \"date\":date, # 指定日期,格式为YYYY-MM-DD,如月份和日期小于10,则取个位,如:2012-1-1\n",
    "    }\n",
    "    resp = requests.get(url,params,headers=headers)\n",
    "    resp_json = json.loads(resp.text)\n",
    "    return resp_json"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2024-4-8\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "{'reason': 'Success',\n",
       " 'result': {'data': {'animalsYear': '龙',\n",
       "   'weekday': '星期一',\n",
       "   'lunarYear': '甲辰年',\n",
       "   'lunar': '二月三十',\n",
       "   'year-month': '2024-4',\n",
       "   'date': '2024-4-8',\n",
       "   'suit': '结婚.出行.搬家.签订合同.交易.搬新房.开业.栽种.安床.挂匾.拆卸.出火.收养子女.开光',\n",
       "   'avoid': '祈福.安葬.祭祀.作灶.入殓.探病',\n",
       "   'holiday': '',\n",
       "   'desc': ''}},\n",
       " 'error_code': 0}"
      ]
     },
     "execution_count": 39,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "wannianli.invoke({\"date\":'2024-04-08'})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2024-04-16\n"
     ]
    }
   ],
   "source": [
    "import time\n",
    "# 获取当前日期\n",
    "current_date = time.strftime(\"%Y-%m-%d\", time.localtime())\n",
    "print(current_date)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [],
   "source": [
    "current_date = time.strftime(\"%Y-%m-%d\", time.localtime())\n",
    "rendered_tools = render_text_description([wannianli])\n",
    "system_prompt = f\"\"\"您是一名助理，可以使用以下工具集。 以下是每个工具的名称和说明:\n",
    "\n",
    "{rendered_tools}\n",
    "\n",
    "今天是{current_date},根据用户输入，返回要使用的工具的名称和输入。 将您的响应作为带有'name'和'arguments'键的 JSON blob 返回，“arguments”键对应的值应该是所选函数的输入参数的字典，字典里不要有任何说明,此JSON blob必须是如下格式：```json\n",
    "...\n",
    "```\"\"\"\n",
    "\n",
    "prompt = ChatPromptTemplate.from_messages(\n",
    "    [(\"system\", system_prompt), (\"user\", \"{input}\")]\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [],
   "source": [
    "chain0 = {\"input\":RunnablePassthrough()}|prompt | model | JsonOutputParser() | itemgetter(\"arguments\") | wannianli\n",
    "chain1 = {\"question\":RunnablePassthrough(), \"response\":chain0}|prompt_response_tianqi|model|StrOutputParser()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2024-4-17\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "'明天是农历2024年的甲辰年三月初九，星期三。适宜进行房屋清洁，但其他事情最好避免，因为有“诸事不宜”的说法。请注意日期是公历2024年4月17日。'"
      ]
     },
     "execution_count": 29,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "chain1.invoke(\"明天农历几号\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 多个工具的使用"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [],
   "source": [
    "@tool\n",
    "def add(first_int: int, second_int: int) -> int:\n",
    "    \"将两个整数相加。\"\n",
    "    return first_int + second_int\n",
    "\n",
    "@tool\n",
    "def exponentiate(base: int, exponent: int) -> int:\n",
    "    \"对底数求指数幂。\"\n",
    "    return base**exponent"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [],
   "source": [
    "tools = [add, exponentiate, multiply]\n",
    "\n",
    "\n",
    "def tool_chain(model_output):\n",
    "    tool_map = {tool.name: tool for tool in tools}\n",
    "    chosen_tool = tool_map[model_output[\"name\"]]\n",
    "    return itemgetter(\"arguments\") | chosen_tool"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [],
   "source": [
    "current_date = time.strftime(\"%Y-%m-%d\", time.localtime())\n",
    "rendered_tools = render_text_description(tools)\n",
    "system_prompt = f\"\"\"您是一名助理，可以使用以下工具集。 以下是每个工具的名称和说明:\n",
    "\n",
    "{rendered_tools}\n",
    "\n",
    "今天是{current_date},根据用户输入，返回要使用的工具的名称和输入。 将您的响应作为带有'name'和'arguments'键的 JSON blob 返回，“arguments”键对应的值应该是所选函数的输入参数的字典，字典里不要有任何说明,此JSON blob必须是如下格式：```json\n",
    "...\n",
    "```\"\"\"\n",
    "\n",
    "prompt = ChatPromptTemplate.from_messages(\n",
    "    [(\"system\", system_prompt), (\"user\", \"{input}\")]\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "1237"
      ]
     },
     "execution_count": 33,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "chain = prompt | model | JsonOutputParser() | tool_chain\n",
    "chain.invoke({\"input\": \"3加上1234\"})"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### classwork 3\n",
    "\n",
    "* 仿照上面多工具例子完成，在工具列表中加入"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'南京现在的天气是阴天，气温为23度，湿度为65%，风向是东风，风力等级为3级。未来几天的预报显示，16日和17日还是以阴天为主，温度在16到25摄氏度之间；18日和19日会有多云转阴和小雨转中雨的情况，温度分别在13到22度和17到22度；而20日则是阴天，风向会由东风转为东南风。请注意天气变化，适时增减衣物。'"
      ]
     },
     "execution_count": 37,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "chain1.invoke(\"南京天气怎样\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 62,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "add: add(first_int: int, second_int: int) -> int - 将两个整数相加。\n",
      "exponentiate: exponentiate(base: int, exponent: int) -> int - 对底数求指数幂。\n",
      "multiply: multiply(first_int: int, second_int: int) -> int - 将两个整数相乘。\n",
      "tianqi: tianqi(city: str) -> dict - 查询最近几日的天气情况，包括温度，天气，湿度，风向等\n",
      "wannianli: wannianli(date: str) -> dict - 根据指定日期查询其农历，习俗，星期几，假期，生肖\n"
     ]
    }
   ],
   "source": [
    "print(rendered_tools)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 67,
   "metadata": {},
   "outputs": [],
   "source": [
    "#tools"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 多工具并行使用\n",
    "\n",
    "参考改编自：https://python.langchain.com/docs/use_cases/tool_use/parallel/\n",
    "\n",
    "### classwork4 \n",
    "* 参考上面链接代码完成工具并行运行的改编"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {},
   "outputs": [],
   "source": [
    "from operator import itemgetter\n",
    "from typing import Union\n",
    "\n",
    "from langchain_core.runnables import (\n",
    "    Runnable,\n",
    "    RunnableLambda,\n",
    "    RunnableMap,\n",
    "    RunnablePassthrough,\n",
    ")\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {},
   "outputs": [],
   "source": [
    "current_date = time.strftime(\"%Y-%m-%d\", time.localtime())\n",
    "rendered_tools = render_text_description(tools)\n",
    "system_prompt = f\"\"\"您是一名助理，可以使用以下工具集。 以下是每个工具的名称和说明:\n",
    "\n",
    "{rendered_tools}\n",
    "\n",
    "今天是{current_date},根据用户输入，返回解决用户问题所需的所有工具的名称和输入，按照执行顺序。 将您的响应作为多个带有'name'和'arguments'键的 JSON blob 返回，“arguments”键对应的值应该是所选函数的输入参数的字典，字典里不要有任何说明,此JSON blob必须是如下格式：```json\n",
    "...\n",
    "```\"\"\"\n",
    "\n",
    "prompt = ChatPromptTemplate.from_messages(\n",
    "    [(\"system\", system_prompt), (\"user\", \"{input}\")]\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2024-4-17\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "[{'name': 'wannianli',\n",
       "  'arguments': {'date': '2024-04-17'},\n",
       "  'output': {'reason': 'Success',\n",
       "   'result': {'data': {'animalsYear': '龙',\n",
       "     'weekday': '星期三',\n",
       "     'lunarYear': '甲辰年',\n",
       "     'lunar': '三月初九',\n",
       "     'year-month': '2024-4',\n",
       "     'date': '2024-4-17',\n",
       "     'suit': '房屋清洁.馀事勿取.塞穴',\n",
       "     'avoid': '诸事不宜',\n",
       "     'holiday': '',\n",
       "     'desc': ''}},\n",
       "   'error_code': 0}},\n",
       " {'name': 'tianqi',\n",
       "  'arguments': {'city': '杭州'},\n",
       "  'output': {'reason': '查询成功!',\n",
       "   'result': {'city': '杭州',\n",
       "    'realtime': {'temperature': '24',\n",
       "     'humidity': '73',\n",
       "     'info': '阴',\n",
       "     'wid': '02',\n",
       "     'direct': '东风',\n",
       "     'power': '2级',\n",
       "     'aqi': '91'},\n",
       "    'future': [{'date': '2024-04-16',\n",
       "      'temperature': '15/24℃',\n",
       "      'weather': '小雨转大到暴雨',\n",
       "      'wid': {'day': '07', 'night': '23'},\n",
       "      'direct': '东风转东北风'},\n",
       "     {'date': '2024-04-17',\n",
       "      'temperature': '13/18℃',\n",
       "      'weather': '小雨',\n",
       "      'wid': {'day': '07', 'night': '07'},\n",
       "      'direct': '东北风'},\n",
       "     {'date': '2024-04-18',\n",
       "      'temperature': '12/24℃',\n",
       "      'weather': '晴转多云',\n",
       "      'wid': {'day': '00', 'night': '01'},\n",
       "      'direct': '东风转东北风'},\n",
       "     {'date': '2024-04-19',\n",
       "      'temperature': '14/26℃',\n",
       "      'weather': '小雨',\n",
       "      'wid': {'day': '07', 'night': '07'},\n",
       "      'direct': '南风转西北风'},\n",
       "     {'date': '2024-04-20',\n",
       "      'temperature': '14/23℃',\n",
       "      'weather': '小雨转阴',\n",
       "      'wid': {'day': '07', 'night': '02'},\n",
       "      'direct': '西北风转持续无风向'}]},\n",
       "   'error_code': 0}}]"
      ]
     },
     "execution_count": 41,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "chain0.invoke(\"明天是农历几号，杭州天气怎样，方便出行吗？\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 大模型的函数调用-Function call\n",
    "\n",
    "* 借助了大语言模型对于代码的理解\n",
    "\n",
    "https://blog.csdn.net/weixin_43679037/article/details/136198044\n",
    "\n",
    "https://blog.csdn.net/jsjsjs1789/article/details/136949541\n",
    "\n",
    "\n",
    "openai的工具案例：\n",
    "https://python.langchain.com/docs/use_cases/tool_use/quickstart/\n",
    "\n",
    "* 国内大模型通常没有bind_tools方法：model_with_tools = model.bind_tools([multiply], tool_choice=\"multiply\")\n",
    "\n",
    "* 工具调用还不支持"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "metadata": {},
   "outputs": [],
   "source": [
    "chat_qf = QianfanChatEndpoint(model=\"ERNIE-Bot\")\n",
    "chat_xh = ChatSparkLLM()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 50,
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.utils.function_calling import convert_to_openai_tool\n",
    "\n",
    "formatted_tools = [convert_to_openai_tool(tool) for tool in tools]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[SystemMessagePromptTemplate(prompt=PromptTemplate(input_variables=[], template='You are a helpful assistant')),\n",
       " MessagesPlaceholder(variable_name='chat_history', optional=True),\n",
       " HumanMessagePromptTemplate(prompt=PromptTemplate(input_variables=['input'], template='{input}')),\n",
       " MessagesPlaceholder(variable_name='agent_scratchpad')]"
      ]
     },
     "execution_count": 51,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from langchain import hub\n",
    "prompt = hub.pull(\"hwchase17/openai-tools-agent\")\n",
    "prompt.messages"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain.agents import AgentExecutor, create_openai_tools_agent\n",
    "\n",
    "agent = create_openai_tools_agent(chat_qf, tools, prompt)\n",
    "agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 54,
   "metadata": {},
   "outputs": [],
   "source": [
    "#请仔细与链接上的案例输出比较，这里是失败的它并没有使用工具！\n",
    "agent_executor.invoke({\"input\": \"Take 3 to the fifth power and multiply that by the sum of twelve and three, then square the whole result\"})"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## "
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
